/**
 * 公共方法文件
 */
// ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
/**
 * 动态绑定fontSize函数（用于移动端的适配）
 */
export const newRem = (): void => {
  // 首先执行一次 改变的比例算法 用于第一次进入获取
  changeRem()
  window.addEventListener('resize', changeRem)
}
// 改变的比例算法
const changeRem = (): void => {
  // 改变的比例计算 宽度：750 / fontSize：
  let changeProportion = 7.5
  // 获取目前屏幕的宽度
  let nowWidth = window.innerWidth
  // 计算当前的fontSize的值
  let nowRem = nowWidth / changeProportion
  // 给根元素绑定此计算后的值
  document.documentElement.style.fontSize = nowRem + 'px'
}

/**
 * 像素换算（用于获取此屏幕尺寸下对应的px值）
 * number：传入对应的rem值
 */
// ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
export const remToPx = (number: number): number => {
  // 如果能获取到根元素设置的fontsize
  let remSize =
    parseFloat(
      window?.getComputedStyle(document.documentElement)['fontSize']
    ) || 0
  // 未获取到根元素fontsize 则自己计算rem
  if (!remSize) {
    remSize = 100 / (750 / document.body.clientWidth)
  }
  // 依然未获取设置固定值
  if (!remSize) {
    remSize = 50
  }
  return Math.round(remSize * (number * 1000)) / 1000
}

/**
 * @随机数
 * min：最小筛选值
 * max：最大筛选值
 */
// ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
export const randomNum = (min: number, max: number) => {
  let range = max - min + 1
  let rand = Math.random()
  let num = min + Math.floor(rand * range)
  return num
}

/**
 * @最简单的性能测试函数
 * @fn需要测试的函数体
 * @param需要带入的参数_没有就不填
 * @name此测试的打印名称_可不填
 * 如果是多参数的则将所有参数作为数组成员写入,然后传入此数组即可
 * 如果只有一个参数，且还是个数组，则需要在在此数据外套一层数组包裹即可，如[param:array]
 */
// ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
interface Text<T, C> {
  (fn?: T, param?: C, name?: string): void
}
export const test: Text<Function, unknown> = (fn, param, name = '') => {
  // 开始性能计时
  console.time(name)

  // 执行待测试的方法
  param instanceof Array ? fn(...param) : fn(param)

  // 结束性能计时
  console.timeEnd(name)
}

/**
 * @数据深克隆方法_只针对数组或对象
 * @data传入需要深克隆的数据
 */
interface DeepClone {
  <T>(data: T, json?: boolean): T
}

export const deepClone: DeepClone = <T>(data: T, json = false) => {
  // 如果没有参数时，直接弹出并提示错误信息
  if (!data) {
    console.error('未存在深克隆参数')

    return

    // 判断是否开启了json深克隆模式的开关
  } else if (json) {
    // 是的话，直接进行弹出json好的属性
    return JSON.parse(JSON.stringify(data))
  }

  // 类型声明对象
  let dataStateArray = data instanceof Array
  let dataStateObject = data instanceof Object

  // 如果类型判断出不是数组或对象类型，则进行警告提示，并弹出此数据
  if (!dataStateArray && !dataStateObject) {
    console.warn('此参数不是对象或数组')
    return data
  }

  // 根据 data的数据类型，进行创建对应的深克隆原始类型
  let cloneData = dataStateArray ? [] : {}

  // 判断data是否是数组
  if (data instanceof Array) {
    // 是数组的话，进行循环遍历
    data.forEach((value, index) => {
      // 深克隆成员承接值
      let needValue: typeof value

      // 判断此成员是否是对象或者数组 是的话再次调用本方法
      value instanceof Object || value instanceof Array
        ? (needValue = deepClone(value))
        : (needValue = value)

      // 给对应的位置上进行赋值
      cloneData[index] = needValue
    })

    // 如果data是个对象
  } else if (data instanceof Object) {
    // 对对象进行遍历
    for (const key in data) {
      // 当前对应的data中的值
      let dataValue = data[key]
      // 后续数据承接值
      let needKeyValue: typeof dataValue
      // 判断当前在data中的值是什么类型，如果对象或数组的话，直接调用此方法，否则直接赋值
      dataValue instanceof Object || dataValue instanceof Array
        ? (needKeyValue = deepClone(dataValue))
        : (needKeyValue = dataValue)

      // 在深克隆数据承接对象中赋予对应的参数
      cloneData[key as unknown as string] = needKeyValue
    }
  }

  // 弹出最后的值
  return cloneData
}

/**
 * @判断用户当前设备是否是PC
 * @返回结果为一个布尔值
 * true为是pc，false则不是pc
 */
// ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
export const IsPC = () => {
  //  获取用户当前的设备信息
  let userAgentInfo = navigator.userAgent

  // 创建移动端内容的数组
  let Agents = [
    'Android',
    'iPhone',
    'SymbianOS',
    'Windows Phone',
    'iPad',
    'iPod',
  ]

  // 对 Agents 使用some方法，判断是否有成员和 当前用户设备信息一样,并赋值给 flagPc
  // （注意some方法返回是有成员一样为true，没有为false，所以要在此处取反）
  let flagPc = !Agents.some((value) => {
    return userAgentInfo.includes(value)
  })

  // 弹出结果
  return flagPc
}

/**
 * @计时器类
 * @callback参数为每次执行后回调的方法_会携带返回值
 * @返回结果为一个对象
 * day：天数；hour：小时；minute：分钟；second：秒钟
 * @打点调用方法
 * open:开启循环计时函数
 * end:停止循环计时,返回此时停止的时间对象
 * again:重置循环计时器,返回此时停止的时间对象
 */
// ————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
// 计时器类的 时间内容承接对象
type EstablishTimeTimeObj = {
  [key in EstablishTimeTimeObjContent]: number
}
// 时间内容承接对象属性
type EstablishTimeTimeObjContent = 'day' | 'hour' | 'minute' | 'second' | 'time'

export const EstablishTimer = class {
  timer: NodeJS.Timer // 定时器承接值
  timeObj: EstablishTimeTimeObj // 时间内容承接对象
  nowTime: number // 计时数值
  oneDaySeconds: number // 一天的总秒数
  oneHourSeconds: number // 一小时的总秒数
  oneMinuteSeconds: number // 一分钟的总秒数
  userCallBack: Function // 定时器中的回调函数

  // 构造函数
  // ------------------------------------------------------------------------------
  constructor(callback?: Function) {
    // 时间内容承接对象进行初始化话
    this.timeObj = {
      day: 0,
      hour: 0,
      minute: 0,
      second: 0,
      time: 0,
    }

    // 定时器中的回调函数初始化
    this.userCallBack = callback

    this.nowTime = 0 // 计时数值初始化
    this.oneDaySeconds = 60 * 60 * 24 // 一天的总秒数初始化
    this.oneHourSeconds = 60 * 60 // 一小时的总秒数初始化
    this.oneMinuteSeconds = 60 // 一分钟的总秒数初始化
  }

  /**
   * @开启循环计时函数
   * @定时器值赋予timer
   */
  // ------------------------------------------------------------------------------
  open: () => void = () => {
    // 防止多次open调用，故在每次open时清理一次定时器
    clearInterval(this.timer)
    // 在赋予定时器的值
    this.timer = setInterval(this.calculationTime, 1000)
  }

  /**
   * @停止循环计时
   * @返回此时停止的时间对象
   */
  // ------------------------------------------------------------------------------
  end: () => EstablishTimeTimeObj = () => {
    // 清理定时器
    clearInterval(this.timer)

    // 弹出当前的时间对象
    return this.timeObj
  }

  /**
   * @重置循环计时器
   * @返回此时重置后的时间对象
   */
  // ------------------------------------------------------------------------------
  again: () => EstablishTimeTimeObj = () => {
    clearInterval(this.timer)
    // 重置 计时数值
    this.nowTime = 0
    // 重置 时间内容承接对象
    this.timeObj = {
      day: 0,
      hour: 0,
      minute: 0,
      second: 0,
      time: 0,
    }

    // 弹出重置后的时间处理对象
    return this.timeObj
  }

  /**
   * @定时器内部使用函数
   */
  // ------------------------------------------------------------------------------
  calculationTime: () => void = () => {
    this.nowTime += 1 // 计时数值自增1

    // 计算此时的天数
    // ================================================================
    let needDay = this.nowTime / this.oneDaySeconds //得到天数的值
    let truncDay = Math.trunc(needDay) // 天数取整

    // 计算此时的小时
    // ================================================================
    let needHour =
      (this.nowTime - truncDay * this.oneDaySeconds) / this.oneHourSeconds // 得到小时的值
    let truncHour = Math.trunc(needHour) // 小时取整

    // 计算此时的分数
    // ================================================================
    // 得到分钟的值
    let needMinute =
      (this.nowTime -
        truncDay * this.oneDaySeconds -
        truncHour * this.oneHourSeconds) /
      this.oneMinuteSeconds
    // 分钟取整
    let truncMinute = Math.trunc(needMinute)

    // 计算此时的秒数
    // ================================================================
    let needSeconds =
      this.nowTime -
      truncDay * this.oneDaySeconds -
      truncHour * this.oneHourSeconds -
      truncMinute * this.oneMinuteSeconds

    // 时间内容承接对象 同步对应内容值
    // ================================================================
    this.timeObj.day = truncDay // 天数取整值
    this.timeObj.hour = truncHour // 小时取整值
    this.timeObj.minute = truncMinute // 分钟取整值
    this.timeObj.second = needSeconds // 秒钟取整值
    this.timeObj.time = this.nowTime // 返回当前的时间值

    // 使用传入的回调函数带上当前的时间内容
    this.userCallBack(this.timeObj)
  }
}
